package primitives;

import java.awt.Color;

import javax.vecmath.*;

import shaders.MatteShader;
import shaders.Shader;

import essential.Axis;
import essential.Ray;
import essential.RayCastResult;

public class Triangle extends Primitive {
	private Vector3d u,v;
	public Vertex a,b,c;
	private Vector3d N;
	private Vector3d origin;
	protected Matrix4d transform = new Matrix4d();
	private Matrix4d tmpMat = new Matrix4d();
	private Vector4d tmpVec = new Vector4d();
	double dpa;
	double dpb;
	double dpc;

	//Precached news
	Ray auxRay = new Ray();
	Vector3d w = new Vector3d();

	public Renderable copy() {
		Triangle t = new Triangle(a, b, c);
		t.transform = new Matrix4d(transform);
		t.setShader(getShader().copy());
		return t;
	}
	
	public Triangle() {}
	
	public Triangle(Vector3d a, Vector3d b, Vector3d c) {
		this.a = new Vertex();
		this.b = new Vertex();
		this.c = new Vertex();
		set(a, b, c);
		recalcAABB();
		this.setShader(new MatteShader());
		this.a.uv = new Vector2d(0, 0);
		this.b.uv = new Vector2d(0, 0);
		this.c.uv = new Vector2d(0, 0);
		this.c.normal = new Vector3d(N);
		this.c.normal = new Vector3d(N);
		this.c.normal = new Vector3d(N);
	}
	
	public Triangle(Vertex a, Vertex b, Vertex c) {
		this.a = new Vertex(a);
		this.b = new Vertex(b);
		this.c = new Vertex(c);
		set(a.xyz, b.xyz, c.xyz);
		recalcAABB();
		this.setShader(new MatteShader());
	}
	
	public void recalcAABB() {
		Vector3d vertices[] = new Vector3d[3];
		vertices[0] = this.a.xyz; vertices[1] = this.b.xyz; vertices[2] = this.c.xyz;
		this.aabb = new AxisAlignedBoundingBox(vertices);
	}

	public void set(Vector3d ap, Vector3d bp, Vector3d cp) {
		this.a.xyz = new Vector3d(ap);
		this.b.xyz = new Vector3d(bp);
		this.c.xyz = new Vector3d(cp);
		// vector form triangle a to b
		u = new Vector3d();
		v = new Vector3d();
		u.x = b.xyz.x - a.xyz.x;
		u.y = b.xyz.y - a.xyz.y;
		u.z = b.xyz.z - a.xyz.z;

		// vector form triangle a to c
		v.x = c.xyz.x - a.xyz.x;
		v.y = c.xyz.y - a.xyz.y;
		v.z = c.xyz.z - a.xyz.z;
         
		N = new Vector3d();
		N.cross(u, v);
		N.normalize();

		double x, y, z;	

		x = (a.xyz.x + b.xyz.x + c.xyz.x)/3;
		y = (a.xyz.y + b.xyz.y + c.xyz.y)/3;
		z = (a.xyz.z + b.xyz.z + c.xyz.z)/3;

		origin = new Vector3d(x,y,z);
		
		//Used for uv mapping
		dpa = dist(a.xyz, b.xyz, c.xyz);
		dpb = dist(b.xyz, a.xyz, c.xyz);
		dpc = dist(c.xyz, a.xyz, b.xyz);
	}

	public Vector3d getNormal(Vector3d v) {
		return N;
	}

	public Plane getPlane()
	{
		double w = N.dot(a.xyz);
		return new Plane(N, -w);
	}

	public RayCastResult intersect(Ray ray)
	{
	//	if (aabb.intersection(ray) == RayCastResult.MISS)
	//		return RayCastResult.MISS;
		
		return plainIntersect(ray);
	}
	
	public RayCastResult plainIntersect(Ray ray) {
		Plane p = this.getPlane();

		Double oldImpactDist = ray.getImpactDist();
		Color oldColor = ray.impactColor;
		Renderable oldRend  =ray.getLastRenderable();
		Vector3d oldNorm = ray.impactNormal;

		/*	if(p.intersect(ray) == RayCastResult.MISS)
     		return RayCastResult.MISS;

		//double dist = this.distanceTo(ray.getOrigin());

		*/
		
		if(p.intersect(ray) == RayCastResult.MISS) {
			return RayCastResult.MISS;
		}

		Vector3d P = ray.getIntersectionPoint();

		double uu, uv, vv, wu, wv, D;
		w.x = P.x - a.xyz.x;
		w.z = P.z - a.xyz.z;
		w.y = P.y - a.xyz.y;
        
		uu = u.dot(u);
		uv = u.dot(v);
		vv = v.dot(v);
		wu = w.dot(u);
		wv = w.dot(v);  
		D = uv * uv - uu * vv;

		// get and test parametric coords
		double s, t;
		s = (uv * wv - vv * wu) / D;

		if (s < 0.000 || s > 1)			// I is outside T
		{
			ray.setImpactDist(oldImpactDist);
			ray.impactColor = oldColor;
			ray.setLastRenderable(oldRend);
			ray.impactNormal = oldNorm;
			return RayCastResult.MISS;
		}

		t = (uv * wu - uu * wv) / D;
		if (t < 0.000 || (s + t) > 1)	// I is outside T
		{
			ray.setImpactDist(oldImpactDist);
			ray.impactColor = oldColor;
			ray.setLastRenderable(oldRend);
			ray.impactNormal = oldNorm;
			return RayCastResult.MISS;
		}

		
		if( getShader().hasTexture() ){ 
			//Texturing, UV Mapping
			Vector2d UV = UVMap(P);
			ray.impactColor = getShader().getColor(UV,Shader.MappingType.Triangle);
			
		}else{
			ray.impactColor = getShader().getColor(ray);
		}
			
		
		ray.setLastRenderable(this);
		//ray.setImpactDist(dist);
		ray.impactNormal = getNormal(ray.getIntersectionPoint());
		return RayCastResult.HIT ; 		// I is in T
	}
	
	public Vector2d UVMap(Vector3d r) {
		Vector2d uv = new Vector2d();
		double rad = dist(r, b.xyz, c.xyz);
		double rbd = dist(r, a.xyz, c.xyz);
		double rcd = dist(r, a.xyz, b.xyz);

		uv.x = a.uv.x * (rad/dpa) + b.uv.x * (rbd/dpb) + c.uv.x * (rcd/dpc);
		uv.y = a.uv.y * (rad/dpa) + b.uv.y * (rbd/dpb) + c.uv.y * (rcd/dpc);
		
//		System.out.println(a.uv.x);
//		System.out.println(a.uv.y);
//		System.out.println(b.uv.x);
//		System.out.println(b.uv.y);
//		System.out.println(c.uv.x);
//		System.out.println(c.uv.y);
		
		return uv;
	}
	
	double dist(Vector3d r, Vector3d p0, Vector3d p1) {
		Vector3d p1p0 = new Vector3d(p1); p1p0.sub(p0);
		Vector3d p0r = new Vector3d(p0); p0r.sub(r);
		Vector3d cross = new Vector3d();
		
		cross.cross(p1p0, p0r);
		
		return cross.length() / p1p0.length();
	}
	
	public double distanceTo(Vector3d v) {
		//Aproximation
		Vector3d v0 = new Vector3d(a.xyz);
		Vector3d v1 = new Vector3d(b.xyz);
		Vector3d v2 = new Vector3d(c.xyz);
		v0.sub(v);
		v1.sub(v);
		v2.sub(v);
		
		double d0 = v0.length();
		double d1 = v1.length();
		double d2 = v2.length();
		
		return d0 < d1? (d0 < d2? d0 : d2) : (d1 < d2? d1 : d2);
	}
	public String toString() {
		return "Triangle(" + a + ", " + b + ", " + c + ")";
	}
	@Override
	public Vector3d getOrigin() {
		return origin;
	}

	@Override
	public void rotate(Axis axis, double angles) {
		tmpMat.setIdentity();

		switch(axis) {
		case X:
			tmpMat.rotX(angles);
			break;
		case Y:
			tmpMat.rotY(angles);
			break;
		case Z:
			tmpMat.rotZ(angles);
			break;
		}

 		transform.mul(tmpMat, transform);
 	}
	
	@Override
	public void scale(Vector3d v) {
		tmpMat.setIdentity();
		tmpMat.m00 = v.x;
		tmpMat.m11 = v.y;
		tmpMat.m22 = v.z;
		transform.mul(tmpMat, transform);
	}
	
	@Override
	public void translate(Vector3d pos) {
		tmpMat.setIdentity();
		tmpMat.setTranslation(pos);
		transform.mul(tmpMat, transform);
	}

	@Override
	public void freezeTransform() {
		tmpVec.set(a.xyz);
		tmpVec.w = 1.0;
		transform.transform(tmpVec);
		a.xyz.set(tmpVec.x, tmpVec.y, tmpVec.z);

		tmpVec.set(b.xyz);
		tmpVec.w = 1.0;
		transform.transform(tmpVec);
		b.xyz.set(tmpVec.x, tmpVec.y, tmpVec.z);

		tmpVec.set(c.xyz);
		tmpVec.w = 1.0;
		transform.transform(tmpVec);
		c.xyz.set(tmpVec.x, tmpVec.y, tmpVec.z);

		set(a.xyz, b.xyz, c.xyz);
		recalcAABB();
	}

	@Override
	public void apply(Transform t) {
		transform.mul(t.matrix, transform);
	}

	@Override
	public void set(Transform t) {
		transform.setIdentity();
		transform.mul(t.matrix, transform);
	}
}
